package com.zenithmind.user.security.impl;

import com.zenithmind.user.security.TokenService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * JWT令牌服务实现
 * 遵循单一职责原则：专门负责JWT令牌的生成、验证和管理
 * 遵循开闭原则：实现TokenService接口，便于扩展
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class JwtTokenService implements TokenService {
    
    private final JwtEncoder jwtEncoder;
    private final JwtDecoder jwtDecoder;
    private final StringRedisTemplate redisTemplate;
    
    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri:http://localhost:8081}")
    private String issuer;
    
    @Value("${jwt.access-token-expiration:3600}")
    private int accessTokenExpiration = 3600; // 1小时
    
    @Value("${jwt.refresh-token-expiration:604800}")
    private int refreshTokenExpiration = 604800; // 7天
    
    private static final String REVOKED_TOKEN_PREFIX = "revoked_token:";
    private static final String REFRESH_TOKEN_PREFIX = "refresh_token:";
    
    @Override
    public String generateAccessToken(Map<String, Object> claims) {
        return generateToken(claims, Duration.ofSeconds(accessTokenExpiration));
    }
    
    @Override
    public String generateRefreshToken(Map<String, Object> claims) {
        String refreshToken = generateToken(claims, Duration.ofSeconds(refreshTokenExpiration));
        
        // 存储刷新令牌到Redis
        String userId = (String) claims.get("user_id");
        if (userId != null) {
            String key = REFRESH_TOKEN_PREFIX + userId;
            redisTemplate.opsForValue().set(key, refreshToken, refreshTokenExpiration, TimeUnit.SECONDS);
        }
        
        return refreshToken;
    }
    
    @Override
    public String generateToken(Map<String, Object> claims, Duration expiration) {
        Instant issuedAt = Instant.now();
        Instant expiresAt = issuedAt.plus(expiration);
        
        JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder()
                .issuer(issuer)
                .issuedAt(issuedAt)
                .expiresAt(expiresAt)
                .id(UUID.randomUUID().toString());
        
        // 添加标准声明
        if (claims.containsKey("sub")) {
            claimsBuilder.subject((String) claims.get("sub"));
            claims = new HashMap<>(claims);
            claims.remove("sub"); // 避免重复
        }
        
        // 添加自定义声明
        claims.forEach(claimsBuilder::claim);
        
        try {
            String token = jwtEncoder.encode(JwtEncoderParameters.from(claimsBuilder.build())).getTokenValue();
            log.debug("生成JWT令牌成功，过期时间: {}", expiration);
            return token;
        } catch (Exception e) {
            log.error("生成JWT令牌失败: {}", e.getMessage());
            throw new RuntimeException("令牌生成失败", e);
        }
    }
    
    @Override
    public TokenInfo validateToken(String token) {
        if (token == null || token.trim().isEmpty()) {
            return new TokenInfo("令牌不能为空");
        }
        
        // 检查令牌是否被撤销
        if (isTokenRevoked(token)) {
            return new TokenInfo("令牌已被撤销");
        }
        
        try {
            Jwt jwt = jwtDecoder.decode(token);
            
            // 检查令牌是否过期
            if (jwt.getExpiresAt() != null && jwt.getExpiresAt().isBefore(Instant.now())) {
                return new TokenInfo("令牌已过期");
            }
            
            Map<String, Object> claims = new HashMap<>(jwt.getClaims());
            String subject = jwt.getSubject();
            
            log.debug("令牌验证成功，用户: {}", subject);
            return new TokenInfo(subject, claims);
        } catch (JwtException e) {
            log.warn("令牌验证失败: {}", e.getMessage());
            return new TokenInfo("令牌无效: " + e.getMessage());
        } catch (Exception e) {
            log.error("令牌验证异常: {}", e.getMessage());
            return new TokenInfo("令牌验证异常");
        }
    }
    
    @Override
    public String refreshAccessToken(String refreshToken) {
        TokenInfo tokenInfo = validateToken(refreshToken);
        
        if (!tokenInfo.isValid()) {
            throw new IllegalArgumentException("刷新令牌无效: " + tokenInfo.getErrorMessage());
        }
        
        String userId = tokenInfo.getUserId();
        if (userId == null) {
            throw new IllegalArgumentException("刷新令牌中缺少用户ID");
        }
        
        // 验证刷新令牌是否存在于Redis中
        String key = REFRESH_TOKEN_PREFIX + userId;
        String storedToken = redisTemplate.opsForValue().get(key);
        if (!refreshToken.equals(storedToken)) {
            throw new IllegalArgumentException("刷新令牌不匹配");
        }
        
        // 生成新的访问令牌
        Map<String, Object> claims = new HashMap<>(tokenInfo.getClaims());
        claims.remove("exp"); // 移除过期时间，让新令牌重新设置
        claims.remove("iat"); // 移除签发时间
        claims.remove("jti"); // 移除JWT ID
        
        String newAccessToken = generateAccessToken(claims);
        log.info("刷新访问令牌成功，用户: {}", userId);
        
        return newAccessToken;
    }
    
    @Override
    public void revokeToken(String token) {
        try {
            Jwt jwt = jwtDecoder.decode(token);
            String jti = jwt.getId();
            
            if (jti != null) {
                String key = REVOKED_TOKEN_PREFIX + jti;
                // 设置过期时间为令牌的剩余有效期
                long ttl = jwt.getExpiresAt() != null ? 
                    Duration.between(Instant.now(), jwt.getExpiresAt()).getSeconds() : 
                    accessTokenExpiration;
                
                if (ttl > 0) {
                    redisTemplate.opsForValue().set(key, "revoked", ttl, TimeUnit.SECONDS);
                    log.info("令牌已撤销: {}", jti);
                }
            }
        } catch (Exception e) {
            log.warn("撤销令牌失败: {}", e.getMessage());
        }
    }
    
    @Override
    public boolean isTokenRevoked(String token) {
        try {
            Jwt jwt = jwtDecoder.decode(token);
            String jti = jwt.getId();
            
            if (jti != null) {
                String key = REVOKED_TOKEN_PREFIX + jti;
                return redisTemplate.hasKey(key);
            }
        } catch (Exception e) {
            log.debug("检查令牌撤销状态失败: {}", e.getMessage());
        }
        
        return false;
    }
    
    /**
     * 清理过期的刷新令牌
     */
    public void cleanupExpiredRefreshTokens() {
        // 这个方法可以通过定时任务调用
        log.debug("清理过期的刷新令牌");
        // 实现清理逻辑...
    }
    
    /**
     * 获取令牌的剩余有效期
     */
    public Duration getTokenRemainingTime(String token) {
        try {
            Jwt jwt = jwtDecoder.decode(token);
            if (jwt.getExpiresAt() != null) {
                return Duration.between(Instant.now(), jwt.getExpiresAt());
            }
        } catch (Exception e) {
            log.debug("获取令牌剩余时间失败: {}", e.getMessage());
        }
        
        return Duration.ZERO;
    }
}
